/*
Copyright (c) 2016 Red Hat, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.ovirt.engine.api.v3;

import static org.ovirt.engine.api.v3.adapters.V3InAdapters.adaptIn;
import static org.ovirt.engine.api.v3.adapters.V3OutAdapters.adaptOut;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.ovirt.engine.api.model.Action;
import org.ovirt.engine.api.restapi.resource.BaseBackendResource;
import org.ovirt.engine.api.v3.types.V3Action;

/**
 * This is the base class for all the implementations of the V3 services. All these implementations have a
 * {@code delegate} field, that contains a reference to equivalent V4 service. The implementation will adapt the
 * input, send it to the V4 service, adapt the output and send it to the caller.
 */
public class V3Server<DELEGATE> {
    /**
     * The reference to the V4 implementation of the service.
     */
    private DELEGATE delegate;

    /**
     * Creates a new V3 implementation.
     *
     * @param delegate the reference to the V4 implementation of the service.
     */
    public V3Server(DELEGATE delegate) {
        Objects.requireNonNull(delegate, "The delegate of a V3 server can't be null");
        this.delegate = delegate;
    }

    /**
     * The reference to the V4 implementation of the service.
     */
    protected DELEGATE getDelegate() {
        return delegate;
    }

    // We need to have the URI info object injected, as we need to manually inject into the V4 implementation of
    // the service.
    @Context
    public void setUriInfo(UriInfo uriInfo) {
        if (delegate instanceof BaseBackendResource) {
            BaseBackendResource resource = (BaseBackendResource) delegate;
            resource.setUriInfo(uriInfo);
        }
    }

    // We need to have the HTTP headers injected, as we need to manually inject into the V4 implementation of the
    // service.
    @Context
    public void setHttpHeaders(HttpHeaders httpHeaders) {
        if (delegate instanceof BaseBackendResource) {
            BaseBackendResource resource = (BaseBackendResource) delegate;
            resource.setHttpHeaders(httpHeaders);
        }
    }

    /**
     * Converts a web application exception generated by V4 of the API into the equivalent V3 exception.
     */
    public static WebApplicationException adaptException(WebApplicationException exception) {
        return new WebApplicationException(adaptResponse(exception.getResponse()));
    }

    /**
     * Converts a response generated by V4 of the API into the equivalent V3 response.
     *
     * @param response the response generated by V4 of the API
     * @return the V3 equivalent response
     */
    public static Response adaptResponse(Response response) {
        if (response == null) {
            return null;
        }
        Object entity = response.getEntity();
        if (entity == null) {
            return response;
        }
        entity = adaptOut(entity);
        return Response.fromResponse(response).entity(entity).build();
    }

    /**
     * Calls a V4 method that generates a response, and adapts the result to V3, including the exceptions that may
     * be thrown by the method.
     *
     * @param method the method to call
     * @return the V3 equivalent response
     */
    public static Response adaptResponse(Supplier<Response> method) {
        try {
            return adaptResponse(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code get} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code get} method to call
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptGet(Supplier<V4> method) {
        try {
            return adaptOut(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code list} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code list} method to call
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptList(Supplier<V4> method) {
        try {
            return adaptOut(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Adapts the given {@code object} to V4 of the API, then calls a V4 {@code add} method to add it, and adapts the
     * result to V3, including the exceptions that may be thrown by the method.
     *
     * @param method the {@code add} method to call
     * @param object the object to pass to the method as parameter
     * @return the V3 equivalent response
     */
    public static <V3, V4> Response adaptAdd(Function<V4, Response> method, V3 object) {
        try {
            return adaptResponse(method.apply(adaptIn(object)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Adapts the given {@code object} to V4 of the API, then calls a V4 {@code add} method to add it, and adapts the
     * result to V3, including the exceptions that may be thrown by the method.
     *
     * @param method the {@code update} method to call
     * @param object the object to pass to the method as parameter
     * @return the V3 equivalent response
     */
    public static <V3, V4> V3 adaptUpdate(Function<V4, V4> method, V3 object) {
        try {
            return adaptOut(method.apply(adaptIn(object)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 {@code remove} method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the {@code remove} method to call
     * @return the V3 equivalent response
     */
    public static Response adaptRemove(Supplier<Response> method) {
        try {
            return adaptResponse(method.get());
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }

    /**
     * Calls a V4 action method and adapts the result to V3, including the exceptions that may be thrown by the
     * method.
     *
     * @param method the action method to call
     * @param action the action to pass to the action method as input parameter
     * @return the V3 equivalent response
     */
    public static Response adaptAction(Function<Action, Response> method, V3Action action) {
        try {
            return adaptResponse(method.apply(adaptIn(action)));
        } catch(WebApplicationException exception) {
            throw adaptException(exception);
        }
    }
}
